package io.github.loser.processor;

import io.github.loserya.aop.ProxyUtil;
import io.github.loserya.aop.interceptor.MogoAopParams;
import io.github.loserya.aop.interceptor.MogoIntercept;
import io.github.loserya.core.sdk.impl.MogoServiceImpl;
import io.github.loserya.global.cache.MogoEnableCache;
import io.github.loserya.global.cache.MongoTemplateCache;
import io.github.loserya.hardcode.constant.MogoConstant;
import io.github.loserya.module.datasource.MongoDs;
import io.github.loserya.module.logic.IgnoreLogic;
import io.github.loserya.module.transaction.MogoTransaction;
import io.github.loserya.utils.AnnotationUtil;
import io.github.loserya.utils.ClassUtil;
import io.github.loserya.utils.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.ResolvableType;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.Repository;

import java.lang.reflect.Field;


/**
 * 判断是否是MogoServiceImpl并生成自定义代理类
 *
 * @author loser
 * @since 1.1.8
 */
public class MogoBeanProcessor implements BeanPostProcessor {

    private static final Log LOGGER = LogFactory.getLog(MogoBeanProcessor.class);
    private final AutowireCapableBeanFactory beanFactory;
    private final ConfigurableListableBeanFactory configurableListableBeanFactory;

    public MogoBeanProcessor(AutowireCapableBeanFactory beanFactory, ConfigurableListableBeanFactory configurableListableBeanFactory) {
        this.beanFactory = beanFactory;
        this.configurableListableBeanFactory = configurableListableBeanFactory;
    }

    /**
     * 递归获取目标对象，如果目标对象也是代理对象，则继续解析直到获取真实对象
     *
     * @param proxy 代理对象
     * @return 真实对象
     * @throws Exception 如果获取真实对象失败
     */
    public static Object getUltimateTargetObject(Object proxy) throws Exception {

        if (AopUtils.isAopProxy(proxy) && proxy instanceof Advised) {
            Advised advised = (Advised) proxy;
            Object target = advised.getTargetSource().getTarget();
            return getUltimateTargetObject(target);
        }
        return proxy;

    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        // 针对 MongoRepositoryFactoryBean 对象数据源替换
        handleRepositoryFactoryBean(bean, beanName);

        // 只针对 MogoServiceImpl 字类实现代理
        if (!(bean instanceof MogoServiceImpl)) {
            return bean;
        }
        // 如果 逻辑删除、多数据源、事务都没开启 则不生成代理对象
        if (!MogoEnableCache.logic && !MogoEnableCache.dynamicDs && !MogoEnableCache.transaction) {
            return bean;
        }
        Class<?> clazz = bean.getClass();
        MogoAopParams mogoAopParams = new MogoAopParams.Builder()
                .classAnnoDs(clazz.getAnnotation(MongoDs.class))
                .classAnnoTs(clazz.getAnnotation(MogoTransaction.class))
                .classAnnoIgnore(clazz.getAnnotation(IgnoreLogic.class))
                .methodMapperDs(AnnotationUtil.buildByClass(clazz, MongoDs.class))
                .methodMapperTs(AnnotationUtil.buildByClass(clazz, MogoTransaction.class))
                .methodMapperIgnore(AnnotationUtil.buildByClass(clazz, IgnoreLogic.class))
                .build();
        if (mogoAopParams.isIgnore()) {
            return bean;
        }
        // 手动走一遍依赖注入
        Object proxy = ProxyUtil.proxy(bean, new MogoIntercept(mogoAopParams));
        beanFactory.autowireBean(proxy);
        return proxy;

    }

    @SuppressWarnings("all")
    private void handleRepositoryFactoryBean(Object bean, String beanName) {

        if (!MogoEnableCache.dynamicDs || !MogoEnableCache.repositoryDs) {
            return;
        }
        if (bean instanceof MongoRepositoryFactoryBean) {
            try {

                // 01 获取 dao 对应的 class 以及 集合 class
                BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanName);
                ResolvableType resolvableType = beanDefinition.getResolvableType();
                ResolvableType genericType = resolvableType.getGeneric(0);
                Class<?> daoClass = genericType.resolve();
                Class<?> entityClass = ClassUtil.getTClassByClass(daoClass, 0);
                String ds = MongoTemplateCache.getEntityDs(entityClass);
                if (StringUtils.isEmpty(ds)) {
                    return;
                }

                // 02 获取 repository 对象并替换数据源
                MongoTemplate mongoTemplate = MongoTemplateCache.getMongoTemplate(ds);
                MongoRepositoryFactoryBean factoryBean = (MongoRepositoryFactoryBean) bean;
                Repository repository = factoryBean.getObject();
                Object ultimateTargetObject = getUltimateTargetObject(repository);
                Field field = ultimateTargetObject.getClass().getDeclaredField("mongoOperations");
                field.setAccessible(true);
                field.set(ultimateTargetObject, mongoTemplate);
                if (MogoEnableCache.debugLog) {
                    LOGGER.info(MogoConstant.LOG_PRE + String.format("mogo MongoRepository [%s] change ds finish [%s]", daoClass.getName(), ds));
                }
            } catch (Exception ignore) {
                LOGGER.info(MogoConstant.LOG_PRE + "mogo MongoRepository change ds error", ignore);
            }
        }

    }

}